﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using Lextm.SharpSnmpLib;
using Lextm.SharpSnmpLib.Messaging;
using System.Net.NetworkInformation;
namespace devicesDll
{
    public delegate void WriteProgress(QueryProgress process);
    public delegate void WriteDebugMessage(string message);

    public class Devices:List<Device>
    {
        #region Constants
        const int ThreadSleepTime = 100;
        const int pingTimeout = 1;
        #endregion

        #region Variables
        int maxThreads = 10;
        QueryQueue queryQueue = new QueryQueue();
        Thread[] activeThreads;
        CustomThread[] customThreads;
        IPAddress[] dhcpNetAddresses;
        IPAddress dhcpServerAddress;
        string[,] dhcpList;
        WriteProgress wp = null;
        WriteDebugMessage wdm = null;
        int timeout = 0;
        int snmpPort = 161;
        CustomQuery[] customQueriesCollection;
        bool queryRunning = false;
        volatile bool blockFullScanExecution = false;
        DeviceSignature[] newDevices;
        private object lockObject = new object();
        List<Device> oldDev;
        #endregion

        #region constructor
        public Devices(IpVlan[] switchList, int timeout, IPAddress[] DHCPNetAddresses, IPAddress DHCPServerAddress)
        {
            Initialize(switchList, timeout, DHCPNetAddresses, DHCPServerAddress);
        }

        public Devices(IpVlan[] switchList, int timeout, IPAddress[] DHCPNetAddresses, IPAddress DHCPServerAddress, int maxThreads)
        {
            this.maxThreads = maxThreads;
            Initialize(switchList,timeout, DHCPNetAddresses,DHCPServerAddress);
        }

        /// <summary>
        /// initializes a few elements which must be configured at every constructor
        /// </summary>
        private void Initialize(IpVlan[] switchList, int timeout, IPAddress[] DHCPNetAddresses, IPAddress DHCPServerAddress)
        {
            this.timeout = timeout;
            activeThreads = new Thread[maxThreads];
            customThreads = new CustomThread[maxThreads];
            dhcpNetAddresses = DHCPNetAddresses;
            dhcpServerAddress = DHCPServerAddress;
            queryQueue.ElementsAdded += new ElementAddedEventHandler(queryQueue_ElementsAdded);
            if (switchList != null)
                foreach (IpVlan item in switchList)
                {
                    Switch newSwitch = new Switch(item.Ip, item.Vlan);
                    newSwitch.Timeout = timeout;
                    this.Add(newSwitch);
                }
            else
                throw new ArgumentNullException("switchAddresses");

        }
        #endregion

        #region Methods
        /// <summary>
        /// is called when a new query is addet to the queue
        /// </summary>
        /// <param name="sender">sender object</param>
        /// <param name="e">event Args: not used at the moment</param>
        private void queryQueue_ElementsAdded(object sender, EventArgs e)
        {
            for (int i = 0; i < maxThreads; i++) //for every Thread
            {
                if (queryQueue.Count > 0) //if a another query is in the queue
                {
                    if (activeThreads[i] != null && activeThreads[i].IsAlive) //if thread is already alive
                    {
                        if (!customThreads[i].IsWorking) //if not working
                        {
                            SingleQuery query;
                            lock (lockObject)
                            {
                                query = queryQueue[0]; //copy query
                                queryQueue.RemoveAt(0); //delete copied query immediately
                            }
                            customThreads[i].SetNewQuery(query.CurrentDevice, query.SwitchOnly, query.QueryString); //set new
                        }
                    }
                    else    //if thread is not running
                    {
                        queryRunning = true;
                        SingleQuery query;
                        lock (lockObject)
                        {
                            query = queryQueue[0]; //copy query
                            queryQueue.RemoveAt(0); //delet copied query immediately
                        }
                        RestartThread(query.CurrentDevice, query.SwitchOnly, query.QueryString, i); //restart current Thread
                    }
                }
                else //if queue is empty
                    break;
            }
        }

        /// <summary>
        /// (Re)starts a Thread with defined informations for a qurey
        /// </summary>
        /// <param name="device">current device</param>
        /// <param name="switchOnly">if only switches should be queried</param>
        /// <param name="query">query</param>
        /// <param name="i">index of the thread</param>
        private void RestartThread(Device device, bool switchOnly, string query, int i)
        {
            customThreads[i] = new CustomThread(device, switchOnly, query); //new instance of custom Thread at position i
            customThreads[i].QueryFinished+=new QueryFinishedEventHandler(QueryFinished); //add event handler
            activeThreads[i] = new Thread(customThreads[i].QueryThread); //initialize in new thread at position i
            activeThreads[i].Start(); //start
            while (!activeThreads[i].IsAlive) ; //wait until it is completely started
        }

        /// <summary>
        /// is called when a thread has finished its query
        /// </summary>
        /// <param name="sender">sender Thread</param>
        /// <param name="e">Event Args: not used at the moment</param>
        private void QueryFinished(object sender, EventArgs e)
        {
            bool stopThread;
            CustomThread senderThread = (CustomThread)sender; //convert sender object to customThread sender-object
            SingleQuery query = null;

            lock (lockObject)
            {
                if (queryQueue.Count > 0) //if any queries are in the queue
                {
                    query = queryQueue[0]; //copy query object
                    queryQueue.RemoveAt(0);     //delete copied query immiediately
                    stopThread = false;
                }
                else //if queue is empty, stop thread
                {
                    stopThread = true;
                }
            }
            if (stopThread)
            {

                #region Calculate number of running threads
                int countRunningThreads = 0;
                foreach (Thread item in activeThreads)
                {
                    if (item != null && item.IsAlive)
                        countRunningThreads++;
                }
                #endregion

                if (countRunningThreads <= 1)
                    queryRunning = false;

                senderThread.StopThread();
            }
            else
                senderThread.SetNewQuery(query.CurrentDevice, query.SwitchOnly, query.QueryString); //send new query to thread
        }

        /// <summary>
        /// A full Scan including Query Switches, request DHCP server and Query other network devices
        /// </summary>
        /// <returns>Returns true if the Scan is successful</returns>
        public bool FullScan()
        {
            QueryProgress currentProcess = QueryProgress.None;
            if (wp != null)
                wp(currentProcess);

            #region DHCP query
            try
            {
                DhcpLookUp();
                currentProcess |= QueryProgress.DHCPSuccessfull;
            }
            catch (Exception ex)
            {
                currentProcess |= QueryProgress.DHCPError;
                if (wp != null)
                    wp(currentProcess);
                throw ex;
            }
            if (wp != null)
                wp(currentProcess);

            if (wdm != null)
            {
                wdm("After DHCP Query:\n" + this.ToString());
                string dhcpResults = "";
                for (int i = 0; i < dhcpList.Length / 2; i++)
                {
                    dhcpResults += " " + dhcpList[i, 0] + "   " + dhcpList[i, 1] + "\n";
                }
                wdm("DHCP List:\n" + dhcpResults);
            }
            #endregion

            #region ping devices
            Ping p = new Ping();
            string errorstring = "";
            for (int i = 0; i < dhcpList.Length / 2; i++)
            {
                try
                {
                    p.Send(IPAddress.Parse(dhcpList[i, 0]), pingTimeout);
                }
                catch (Exception ex) { errorstring += ex.Message + "\r\n\r\n"; }
            }
            if (wdm != null && errorstring != "")
                wdm(errorstring);

            #endregion

            #region Switch Query
            List<SingleQuery> queries = new List<SingleQuery>();

            foreach (Switch sw in this.OfType<Switch>())
            {
                queries.Add(new SingleQuery(sw, true, "default"));
            }
            if (queries.Count == 0)
            {
                currentProcess |= QueryProgress.SwitchError;
                if (wp != null)
                    wp(currentProcess);
                throw new ApplicationException("Switch Query failed");
            }
            queryQueue.AddQueryRange(queries.ToArray());

            while (queryRunning) Thread.Sleep(100);

            #region Get Switch SuccessRate
            int numberOfSwitches = 0;
            int errorSwitches = 0;
            foreach (Switch sw in this.OfType<Switch>())
            {
                numberOfSwitches++;
                if (sw.ConnectionFailed == true)
                    errorSwitches++;
            }
            if (errorSwitches == 0)
            {
                currentProcess |= QueryProgress.SwitchSuccessfull;
            }
            else if (errorSwitches == numberOfSwitches)
            {
                currentProcess |= QueryProgress.SwitchError;
                if (wp != null)
                    wp(currentProcess);
                throw new ApplicationException("Switch Query failed \nNumber of Switches: "+numberOfSwitches);
            }
            else
            {
                currentProcess |= QueryProgress.SwitchWarning;
            }
            if (wp != null)
                wp(currentProcess);
            #endregion
            if (wdm != null)
                wdm("After Switch Query:\n"+this.ToString());
            #endregion
            
            #region resolve DHCP and Switch results
            List<string> macList = new List<string>();
            List<string> portList = new List<string>();
            List<IPListEntry> ipList = new List<IPListEntry>();
            foreach (Switch sw in this.OfType<Switch>())
            {
                if (sw.Mac != null && sw.Port != null)
                {
                    macList.AddRange(sw.Mac);
                    portList.AddRange(sw.Port);
                }
            }
            for (int i = 0; i < macList.Count; i++)
            {

                for (int n = 0; n < dhcpList.Length / 2; n++)
                {
                    if (macList[i] == dhcpList[n, 1])
                    {
                        ipList.Add(new IPListEntry(dhcpList[n, 0], i));
                    }
                }
            }
            #endregion

            #region Update Local Database
            #region update/create Device objects
            List<Device> devicesToIdentifyAgain = (from dev in this where dev.GetType() == typeof(Device) select dev).ToList();

            foreach (Device de in devicesToIdentifyAgain)
                this.Remove(de);

            List<DeviceSignature> devicesToIdentify = new List<DeviceSignature>();
            List<IPCollection> recheckIps = new List<IPCollection>();
            for (int i = 0; i < macList.Count; i++)
            {
                Device d = (from dev in this where dev.Mac != null && dev.Mac.Length == 1 && dev.Mac[0] == macList[i] && dev.GetType() != typeof(Switch) select dev).FirstOrDefault();
                string[] newIps = (from ip in ipList where ip.Index == i select ip.IP).ToArray();
                if (d == default(Device))
                {
                    DeviceSignature newDev = new DeviceSignature(newIps, macList[i], portList[i]);
                    if(!devicesToIdentify.Contains(newDev))
                        devicesToIdentify.Add(newDev);
                }
                else //update Device
                {
                    d.Port = new string[1];
                    d.Port[0] = portList[i];
                    recheckIps.Add(new IPCollection(newIps, d));
                }

            }
            #region update Devices
            IdentifyDevices identifyClass = new IdentifyDevices();
            identifyClass.IdentifyDevicesFinished += new AsyncIdentifyDevicesFinishedEventHandler(identifyClass_IdentifyDevicesFinished);
            identifyClass.RecheckDevicesFinished += new AsyncRecheckDevicesFinishedEventHandler(identifyClass_RecheckDevicesFinished);
            blockFullScanExecution = true;
            identifyClass.StartAsyncIPCheck(recheckIps, maxThreads, timeout);
            while (blockFullScanExecution) Thread.Sleep(ThreadSleepTime);

            #endregion

            #region Identify Device Types
            blockFullScanExecution = true;
            identifyClass.StartAsyncIdentify(devicesToIdentify,maxThreads,snmpPort,timeout);
            while (blockFullScanExecution) Thread.Sleep(ThreadSleepTime);
            #endregion 

            #region Create new Devices
            foreach (DeviceSignature dev in newDevices)
            {
                IPAddress address;
                try
                {
                    address = IPAddress.Parse(dev.ip[0]);
                }
                catch
                {
                    address = null;
                }
                switch (dev.type)
                {
                    case DeviceType.PC:
                        this.Add(new Pc(dev.mac, address, dev.port));

                        break;

                    case DeviceType.Printer:
                        this.Add(new Printer(dev.mac, address, dev.port));
                        break;

                    case DeviceType.Device:
                        this.Add(new Device(dev.mac, address, dev.port));
                        break;
                    case DeviceType.unreachableDevice:
                        this.Add(new Device(dev.mac, address, dev.port, true));
                        break;
                }
            }
            newDevices = new DeviceSignature[0];
            #endregion
            #endregion
            
            oldDev = (from dev in this where dev.GetType() != typeof(Switch) && dev.Mac != null && !macList.Contains(dev.Mac[0]) select dev).ToList();

            foreach (Device de in oldDev)
                this.Remove(de);

            currentProcess |= QueryProgress.DataBaseSuccessfull;
            if (wp != null)
                wp(currentProcess);

            if (wdm != null)
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine("After Update LocalDatabase:\n" + this.ToString());
                sb.AppendLine("Internal Lists:");
                for (int i = 0; i < macList.Count; i++)
                {
                    string[] ips = (from ip in ipList where ip.Index == i select ip.IP).ToArray();
                    string strIps = "";
                    foreach (string item in ips)
                    {
                        strIps += item + "\n";
                    }

                    sb.AppendFormat("{0} : {1} : {2}\n", strIps, macList[i], portList[i]);
                }
                wdm(sb.ToString());
            }
            
            #endregion

            #region Query Devices
            List<SingleQuery> singleQueryCollection = new List<SingleQuery>();
            foreach (Device dev in this.OfType<Pc>())
            {
                string querystring = "default";
                if (customQueriesCollection != null)
                {
                    foreach (CustomQuery q in customQueriesCollection)
                    {
                        if (q.Group == DeviceType.PC || q.Group == DeviceType.Device)
                            querystring += ";" + q.Query;
                    }
                }
                singleQueryCollection.Add(new SingleQuery(dev,false,querystring));
            }

            foreach (Device dev in this.OfType<Printer>())
            {
                string querystring = "default";
                if (customQueriesCollection != null)
                {
                    foreach (CustomQuery q in customQueriesCollection)
                    {
                        if (q.Group == DeviceType.Printer || q.Group == DeviceType.Device)
                            querystring += ";" + q.Query;
                    }
                }
                singleQueryCollection.Add(new SingleQuery(dev, false, querystring));
            }

            queryQueue.AddQueryRange(singleQueryCollection.ToArray());


            while (queryRunning) Thread.Sleep(ThreadSleepTime) ;

            if (wdm != null)
                wdm("After Device Queries:\n" + this.ToString());

            currentProcess |= QueryProgress.DeviceQueriesSuccessfull;
            if (wp != null)
                wp(currentProcess);
            #endregion

            return true; 
        }

        /// <summary>
        /// is called when the asynchronus recheck ip Adresses progrogress is completed
        /// </summary>
        /// <param name="sender">sender object, which called the event</param>
        /// <param name="e">Arguments of the Event</param>
        void identifyClass_RecheckDevicesFinished(object sender, EventArgs e)
        {
            blockFullScanExecution = false;
        }

        /// <summary>
        /// is called when the asynchronus identify device progrogress is completed
        /// </summary>
        /// <param name="sender">sender object, which called the event</param>
        /// <param name="e">Arguments of the Event</param>
        void identifyClass_IdentifyDevicesFinished(object sender, IdentifyDevicesEventArgs e)
        {
            newDevices = e.Devices;
            blockFullScanExecution = false;
        }

        /// <summary>
        /// Returns a device wich has the given IP Address
        /// </summary>
        /// <param name="ip">IP Address of the device</param>
        /// <returns>Searched device</returns>
        private Device SearchDevice(IPAddress ip)
        {
            Device currentDevice = null; //set current device at default
            foreach (Device d in this) //search device whith the same IP address
            {
                if (d.IP == ip)
                {
                    currentDevice = d;
                    break;
                }
            }

            if (currentDevice == null) //if not found, return
                throw new ArgumentException("Device not found", "ip");
            return currentDevice;
        }

        #region Scan IP overloads
        /// <summary>
        /// scan one Ip with one query
        /// </summary>
        /// <param name="ip">IP Adress of the device</param>
        /// <param name="query">query, which should be executed</param>
        public void ScanIP(IPAddress ip, string query)
        {
            Device currentDevice = SearchDevice(ip); //get the device with the current IP address
            queryQueue.AddQuery(new SingleQuery(currentDevice, false, query)); //add query to queue
        }

        /// <summary>
        /// Scan one IP Address with many queries
        /// </summary>
        /// <param name="ip">IP Adress of the device</param>
        /// <param name="query">queries, which should be executed</param>
        public void ScanIP(IPAddress ip, string[] query)
        {
            string combinedQuery = "";
            foreach (string q in query)
            {
                combinedQuery += q + ";";
            }
            combinedQuery = combinedQuery.Remove(combinedQuery.Length - 1); //removes Last character
            ScanIP(ip, combinedQuery);
        }

        /// <summary>
        /// Scan many IP Adresses with one Query
        /// </summary>
        /// <param name="ip">Ip Adresses</param>
        /// <param name="query">Query</param>
        public void ScanIP(System.Net.IPAddress[] ip, string query)
        {
            foreach (IPAddress ipAdd in ip) //for every IP Address
            {
                ScanIP(ipAdd, query); //add query to queryqueue
            }
        }

        /// <summary>
        /// Scan many IP Adresses with many Queries
        /// </summary>
        /// <param name="ip">IP Adresses</param>
        /// <param name="query">Queries</param>
        public void ScanIP(System.Net.IPAddress[] ip, string[] query)
        {
            foreach (IPAddress ipAdd in ip) //for every IP address
            {
                ScanIP(ipAdd, query); //add queries to queryqueue
            }
        }

        /// <summary>
        /// Scan a range of IPs with one Query
        /// </summary>
        /// <param name="startIP">Included first IP</param>
        /// <param name="endIP">Included last IP</param>
        /// <param name="query">Query</param>
        public void ScanIP(IPAddress startIP, IPAddress endIP, string query)
        {
            int firstIP=ConvertIpToInt(startIP); //converts the first ip to an 32-bit integer
            int lastIP=ConvertIpToInt(endIP); //converts the last ip to an 32-bit integer
            for (int i = firstIP; i <= lastIP; i++) //for every IP Address in the given Range
            {
                IPAddress currentIP = new IPAddress(i); //Convert the number to an IP Address
                ScanIP(currentIP, query); //ScanIP(IPAddress ip, string query)
            }
        }

        /// <summary>
        /// Scan a range of IPs with many queries
        /// </summary>
        /// <param name="startIP">Included first IP</param>
        /// <param name="endIP">Included last IP</param>
        /// <param name="query">Queries</param>
        public void ScanIP(IPAddress startIP, IPAddress endIP, string[] query)
        {
            int firstIP = ConvertIpToInt(startIP); //converts the first ip to an 32-bit integer
            int lastIP = ConvertIpToInt(endIP); //converts the last ip to an 32-bit integer
            for (int i = firstIP; i <= lastIP; i++) //for every IP Address in the given Range
            {
                IPAddress currentIP = new IPAddress(i); //Convert the number to an IP Address
                ScanIP(currentIP, query); //ScanIP(IPAddress ip, string[] query)
            }
            
        }

        /// <summary>
        /// Scan a complete Subnet with one query
        /// </summary>
        /// <param name="ipNet">Net Adress</param>
        /// <param name="subnetmask">Subnetmask</param>
        /// <param name="query">Query</param>
        public void ScanIP(IPAddress ipNet, int subnetmask, string query)
        {
            int convertedIP=ConvertIpToInt(ipNet); //Convert IP net to an 32-bit Integer
            int firstIP = (convertedIP & subnetmask) + 1; //ignore all bits in the host part, and get the first ip in the subnet
            int lastIP = (convertedIP | (~subnetmask)) - 1; //set Ip net Address to broadcast and decreas by 1 --> last IP address
            for (int i = firstIP; i <= lastIP; i++) //for every Address in the Range
            {
                IPAddress currentIP = new IPAddress(i); //Convert the number to an IP Address
                ScanIP(currentIP, query); //ScanIP(IPAddress ip, string query)
            }
            
        }

        /// <summary>
        /// Scan a complete Subnet with many queries
        /// </summary>
        /// <param name="ipNet">Net Adress</param>
        /// <param name="subnetmask">Subnetmask</param>
        /// <param name="query">Queries</param>
        public void ScanIP(IPAddress ipNet, int subnetmask, string[] query)
        {
            int convertedIP = ConvertIpToInt(ipNet); //Convert IP net to an 32-bit Integer
            int firstIP = (convertedIP & subnetmask) + 1; //ignore all bits in the host part, and get the first ip in the subnet
            int lastIP = (convertedIP | (~subnetmask)) - 1; //set Ip net Address to broadcast and decreas by 1 --> last IP address
            for (int i = firstIP; i <= lastIP; i++) //for every Address in the Range
            {
                IPAddress currentIP = new IPAddress(i); //Convert the number to an IP Address
                ScanIP(currentIP, query); //ScanIP(IPAddress ip, string[] query)
            }
        }
        #endregion

        /// <summary>
        /// Converts a given IP Adress to a 32 bit binary Integer
        /// </summary>
        /// <param name="ip">IP Adress</param>
        /// <returns>IP Adress as 32-bit integer</returns>
        public static int ConvertIpToInt(IPAddress ip)
        {
            int convertedIP = 0; //initialize integer
            byte[] byteIP = ip.GetAddressBytes();
            for (byte i = 0; i < 4; i++) //for every byte of the IP address
            {
                convertedIP |= (((byte)byteIP[i]) << ((3 - i) * 8)); //add every Byte in the correct order to the integer
            }
            return convertedIP;
        }
               
        /// <summary>
        /// A Dhcp Request return a [ip,mac] address list from the DHCP server as strings 
        /// </summary>
        private void DhcpLookUp()
        {
            List<string> tempMacIP = new List<string>();       //temporary list to save the data

            foreach (IPAddress dhcpNet in dhcpNetAddresses)
            {
                Process processNetsh = new Process();                                                                                   //new Process
                string filename = Environment.SystemDirectory + @"\NETSH.exe";
                processNetsh.StartInfo.FileName = filename;                                                 //the new Process ist the netsh.exe from windows
                string args = "dhcp server " + dhcpServerAddress + " scope " + dhcpNet + " show clients";   //this is the argumen which the exe will start
                processNetsh.StartInfo.Arguments = args;
                processNetsh.StartInfo.UseShellExecute = false;                                                                         //start no Shell
                processNetsh.StartInfo.RedirectStandardOutput = true;
                processNetsh.StartInfo.RedirectStandardError = true; //
                processNetsh.Start();                                                                                                   //starts the Process

                Regex macIP = new Regex(                                                //Regular Expression which filters the IP to Mac Address
                    @"(?<ip>(\d*\.){3}(\d*)).*(?<mac>[0-9a-fA-F]{2}(\-[0-9a-fA-F]{"
                    + @"2}){5})",
                    RegexOptions.IgnoreCase
                    | RegexOptions.Multiline
                    | RegexOptions.IgnorePatternWhitespace
                    | RegexOptions.Compiled
                    );
                processNetsh.WaitForExit(timeout);
                string killMessage = "Process has Exited";
                string stdOut = processNetsh.StandardOutput.ReadToEnd(); //write the output from the Process in a string
                string errOut = processNetsh.StandardError.ReadToEnd();
                if (!processNetsh.HasExited)
                {
                    if (processNetsh.Responding)
                    {
                        processNetsh.Close();
                        killMessage = "Process Closed";
                    }
                    else
                    {
                        processNetsh.Kill();
                        killMessage = "Process Killed";
                    }
                    //throw new ApplicationException("Timeout occured \nQuery: " + args+"\nPath: "+filename+"\nStandard Output: "+ stdOut +"\nError Output: "+errOut);
                }
                if (errOut != "" || stdOut == "")
                {
                    throw new ApplicationException(killMessage + "\n\nStandard Error: " + errOut + "\nStandard Output: " + stdOut);
                }

                if (wdm != null)
                    wdm(killMessage + "\n\nDHCP Raw Data:\n\n" + stdOut);

                Match matchMacIP = macIP.Match(stdOut);     //filters the IP address
                while (matchMacIP.Success)                  //foreach successe the Ip Address and the Mac Address is saved in the tepMacIP list
                {
                    string ipaddress = matchMacIP.Groups["ip"].ToString();  //Filters the IP Address
                    string macaddress = matchMacIP.Groups["mac"].ToString();//Filters the Mac Address
                    tempMacIP.Add(ipaddress);                               //add the ip address to the list
                    tempMacIP.Add(macaddress);                              //add the mac address to the list
                    matchMacIP = matchMacIP.NextMatch();                    //go to the next match
                }
            }


            dhcpList = new string[tempMacIP.Count / 2, 2];          //creat a array with the right length
            int x = 0;
            for (int i = 0; i < dhcpList.Length / 2; i++)             //saves the list in the array [ip,mac]
            {
                dhcpList[i, 0] = tempMacIP[x];
                dhcpList[i, 1] = tempMacIP[x + 1];
                x = x + 2;
            }

        }

        public override string ToString()
        {
            string thisString = "Devices: \n";
            foreach (Device item in this)
            {
                thisString += " " + item.ToString();
            }
            thisString += "Count: " + this.Count + "\n";
            return thisString;
        }

        #endregion

        #region Properties
        /// <summary>
        /// <para>Get or Set maximum of Parallel query threads</para>
        /// <para>&#160;</para>
        /// <para>Exceptions:</para>
        /// <list><item>&#160; ApplicationException</item></list>
        /// </summary>
        public int MaxThreads 
        { 
            get { return maxThreads; } 
            set 
            {
                bool allowChange = true;
                for (int i = 0; i < activeThreads.Length; i++) //check for every thread
                {
                    if (activeThreads[i] != null && activeThreads[i].IsAlive) //if thread is running
                    {
                        allowChange = false; //block change of the value
                        break;
                    }
                }
                if (allowChange) //if change is allowed
                {
                    maxThreads = value;
                    activeThreads = new Thread[maxThreads]; //reinitialize Arrays with the new length
                    customThreads = new CustomThread[maxThreads]; //reinitialize Arrays with the new length
                }
                else //if change is not allowed
                {
                    throw new ApplicationException("At least one thread is currently running. \r\nValue cannot be changed.");
                }
            } 
        }

        public WriteProgress WriteProgressMethod { set { wp = value; } }

        public WriteDebugMessage WriteDebugMethod { set { wdm = value; } }

        public IPAddress[] DhcpNetAddresses { get { return dhcpNetAddresses; } set { dhcpNetAddresses = value; } }

        public IPAddress DhcpServerAddress { get { return dhcpServerAddress; } set { dhcpServerAddress = value; } }

        public int Timeout 
        { 
            get { return timeout; } 
            set 
            {
                timeout = value;
                foreach (Device item in this)
                {
                    item.Timeout = value;
                }
            } 
        }

        public CustomQuery[] CustomQueriesCollection { get { return customQueriesCollection; } set { customQueriesCollection = value; } }
        
        public int SnmpPort
        {
            get { return snmpPort; }
            set
            {
                snmpPort = value;
                foreach (Switch item in this.OfType<Switch>())
                {
                    item.SnmpPort = value;
                }
            }
        }

        public Device[] RemovedDevices { get { return oldDev.ToArray(); } }
        #endregion

        #region private structs
        private struct IPListEntry
        {
            public IPListEntry(string ip, int index)
            {
                IP = ip;
                Index = index;
            }
            public readonly string IP;
            public readonly int Index;
        }

        #endregion
    }
}
